swarm-0.4: data/scenarios/Challenges/Sliding Puzzles/_sliding-puzzle/solution.sw
/**
Approach:
Place correct tiles in top row and left column,
then recurse into sub-rectangle.
NOTE: Much of this code is experimental and unused.
It may be revisited.
In particular, the Quad-tree search code should probably
be extracted into its own demo scenario.
*/
def elif = \t. \then. \else. {if t then else} end
def else = \t. t end
def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;
def signum = \x.
if (x < 0) {-1}
$ elif (x > 0) {1}
$ else {0};
end;
def mod : int -> int -> int = \i.\m.
i - m * (i / m);
end
def abs = \n. if (n < 0) {-n} {n} end;
def mapTuple = \f. \t.
(f $ fst t, f $ snd t)
end;
def sumTuples = \t1. \t2.
(fst t1 + fst t2, snd t1 + snd t2);
end;
def getOrdinal : text -> cmd int = \item.
count $ item ++ "-ordinal";
end;
/** One-based index
*/
def getLetterEntityByIndex = \idx.
let letter = toChar $ idx - 1 + charAt 0 "a" in
letter ++ "-tile";
end;
def negateTuple = \t.
mapTuple (\x. -x) t;
end;
def subtractTuple = \t1. \t2.
sumTuples t1 $ negateTuple t2;
end;
def getRelativeLocation = \absLoc.
myloc <- whereami;
let negatedLoc = negateTuple myloc in
return $ sumTuples negatedLoc absLoc;
end;
def getRelativeRectangle : (int * int) * (int * int) -> cmd ((int * int) * (int * int)) = \corners.
myloc <- whereami;
let negatedLoc = negateTuple myloc in
return $ mapTuple (sumTuples negatedLoc) corners;
end;
/**
Generates rectangle corners
relative to the player's current location,
given corners specified as absolute GPS coords.
*/
def getBoardRectangle =
getRelativeRectangle ((0, 0), (3, -3));
end;
/** Gets relative location to letter by its ordinal */
def getLetterLocation = \idx.
corners <- getBoardRectangle;
let entName = getLetterEntityByIndex idx in
loc <- detect entName corners;
end;
def moveTuple = \tup.
let x = fst tup in
let y = snd tup in
turn $ if (x > 0) {east} {west};
doN (abs x) move;
turn $ if (y > 0) {north} {south};
doN (abs y) move;
end;
/**
Index is 1-based.
Coordinates are relative to the upper-left
corner of the board (which happens to be at (0,0) world coords),
with down being the negative Y direction.
*/
def getAbsoluteTargetLocationForIndex = \boardWidth. \n.
let idx = n - 1 in
(idx/boardWidth, -(mod idx boardWidth));
end;
/**
The first Boolean argument indicates whether
we should extract the vertical or horizontal dimension.
*/
def getRectDimension = \vertical. \rect.
let extractor = if vertical {snd} {fst} in
let tuple = mapTuple extractor rect in
abs $ snd tuple - fst tuple;
end;
def getOffsets = \rect.
subtractTuple (snd rect) (fst rect);
end;
def getAbsDelta = \rect.
let difference = getOffsets rect in
mapTuple abs difference;
end;
def distance = \loc1. \loc2.
let d = getAbsDelta (loc1, loc2) in
fst d + snd d;
end;
// NOT USED
def selectCloser = \refloc. \loc1. \loc2.
let d1 = distance refloc loc1 in
let d2 = distance refloc loc2 in
if (d1 < d2) {loc1} {loc2}
end;
/**
Get rectangle dimensions as a tuple.
Since the rectangle is specified by "inclusive" corners,
we add one to each dimension of the absolute difference
of corners to obtain the corrected area.
*/
def getDimensions = \rect.
let absDifference = getAbsDelta rect in
mapTuple (\x. x + 1) absDifference;
end;
def getRectArea = \dims.
fst dims * snd dims;
end;
/**
Determines whether the vertical dimension is larger
than the horizontal dimension.
*/
def isVerticalLarger = \dims.
snd dims > fst dims;
end;
/**
Returns a tuple of the two rectangle partitions.
Precondition:
Rectangle corners are specified in the order:
(top left, bottom right)
The "rightward/downward offset" (i.e. +1 horizontally, -1 vertically)
to the "avg" for the second rectangle depends on this assumption.
*/
def splitRectangle = \vertically. \rect.
let cornerA = fst rect in
let cornerB = snd rect in
if vertically {
// TODO: We should "round down" the height of the second partition
// so that the first partition is more likely to contain
// the target (i.e. we perform one fewer iteration).
let avgY = (snd cornerA + snd cornerB) / 2 in
let firstRect = (cornerA, (fst cornerB, avgY)) in
let secondRect = ((fst cornerA, avgY - 1), cornerB) in
(firstRect, secondRect)
} {
let avgX = (fst cornerA + fst cornerB) / 2 in
let firstRect = (cornerA, (avgX, snd cornerB)) in
let secondRect = ((avgX + 1, snd cornerA), cornerB) in
(firstRect, secondRect)
};
end;
def blankCellLocatorCriteria = \rect.
entCount <- density rect;
let dims = getDimensions rect in
let tileCount = getRectArea dims in
return $ entCount < tileCount;
end;
/**
Performs a quad-tree search for the blank square.
Partitioning can be either vertical or horizontal.
We always partition along the largest dimension.
The "first" partition in a vertical split is the "top" partition.
The "first" partition in a horizontal split is the "left" partition.
*/
def findEmptyCell = \foundCriteria. \rect.
let dims = getDimensions rect in
let tileCount = getRectArea dims in
if (tileCount < 1) {
return $ inL ();
} $ elif (tileCount == 1) {
foundHere <- foundCriteria rect;
return $ if foundHere {
inR $ fst rect;
} {
inL ();
};
} $ else {
let isBiggerVertically = isVerticalLarger dims in
let splitted = splitRectangle isBiggerVertically rect in
let firstPartition = fst splitted in
let secondPartition = snd splitted in
foundInFirst <- foundCriteria firstPartition;
let selectedPartition = if foundInFirst {firstPartition} {secondPartition} in
findEmptyCell foundCriteria selectedPartition;
};
end;
/**
Out of two candidate locations, select the one
that is not co-linear with the blank tile location
and the letter location.
It is assumed that one of the candidates will meet
this criteria, so we only need check the first.
*/
def avoidCollinear = \blankloc. \tileloc. \loc1. \loc2.
let firstOk = if (fst blankloc == fst tileloc) {
fst loc1 != fst tileloc
} $ elif (snd blankloc == snd tileloc) {
snd loc1 != snd tileloc
} $ else {true} in
if firstOk {loc1} {loc2};
end;
/**
Move the blank to the cell that allows
the selected letter to advance in the
direction of its target location.
We select a cell with either a vertical offset
or a horizontal offset from the letter tile.
The offset must be in the direction of the
letter's ultimate destination.
*/
def getInitialBlankDestination = \blankLoc. \letterloc. \targetloc.
let getCoord = \f. signum (f targetloc - f letterloc) + f letterloc in
avoidCollinear blankLoc letterloc (getCoord fst, snd letterloc) (fst letterloc, getCoord snd);
end;
def moveSpaceToTile = \blankLoc. \targetRelativeLoc. \letterloc.
log $ "first letter loc: " ++ format letterloc;
// Note: this rectangle might not be "normalized" in terms
// of corner ordering...
let boundingPathRect = (letterloc, targetRelativeLoc) in
log $ "Bounding rect: " ++ format boundingPathRect;
let moveBlankTo = getInitialBlankDestination blankLoc letterloc targetRelativeLoc in
log $ "Move blank to: " ++ format moveBlankTo;
let incrementalBlankDest1 = getInitialBlankDestination letterloc blankLoc moveBlankTo in
log $ "Incremental destination 1: " ++ format incrementalBlankDest1;
moveTuple incrementalBlankDest1;
end;
/**
Algorithm:
1. Bring the empty space into the rotational path of the letter.
2. Rotate the tile along the perimeter of the bounding rectangle.
*/
def placeTile = \boardWidth. \idx. \blankLoc.
// TODO: Bring the empty space into the smallest
// rectangle bounding both the letter and its
// destination.
log $ "empty space loc: " ++ format blankLoc;
eitherLetterloc <- getLetterLocation idx;
let targetLocAbsolute = getAbsoluteTargetLocationForIndex boardWidth idx in
log $ "absolute target loc: " ++ format targetLocAbsolute;
targetRelativeLoc <- getRelativeLocation targetLocAbsolute;
// case eitherLetterloc return $ moveSpaceToTile blankLoc targetRelativeLoc;
moveTuple blankLoc;
end;
def moveManually =
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn left;
drill forward;
move;
turn left;
drill forward;
move;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
drill forward;
move;
turn right;
drill forward;
move;
turn right;
drill forward;
move;
drill forward;
move;
turn right;
drill forward;
end;
def go = \boardWidth.
corners <- getBoardRectangle;
eitherBlankLoc <- findEmptyCell blankCellLocatorCriteria corners;
case eitherBlankLoc return $ placeTile boardWidth 1;
moveManually;
end;
go 3;